using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;

namespace LS.GameKit.SequenceAnimate
{
    public enum SequenceRendererType
    {
        UGuiImage,
        UIElement,
        SpriteRenderer,
        MeshRenderer,
        SkinnedMeshRenderer,
        Material,
    }

    public class SequenceAnimation : MonoBehaviour
    {
        [SerializeField]
        private bool m_PlayOnEnable = true;

        [SerializeField]
        private bool m_UpdateWithoutTimeScale = false;

        [SerializeField]
        private SequenceRendererType m_SequenceRendererType;

        [SerializeField]
        private SpriteRenderer m_SpriteRenderer;

        [SerializeField]
        private MeshRenderer m_MeshRenderer;

        [SerializeField]
        private SkinnedMeshRenderer m_SkinnedMeshRenderer;

        [SerializeField]
        private UnityEngine.UI.Image m_Image;

        [SerializeField]
        private string m_ElementName;

        [SerializeField]
        private UIDocument m_UiDocument = null;

        [SerializeField]
        private Material m_Material = null;

        [SerializeField]
        private List<SequenceClip> m_Clips = new List<SequenceClip>();

        public UnityEvent OnStartPlay = new UnityEvent();
        public UnityEvent OnEndPlay = new UnityEvent();

        private SequenceRenderer m_Renderer;

        private int m_CurrentPlayingIndex = 0;
        private bool m_IsPlaying = false;
        private bool m_IsPause = false;
        private float m_Timer = 0;
        private float m_Speed = 1;
        private float m_Sign = 1;

        private bool m_RendererDurty = true;
        private bool m_UpdateDurty = false;

        public int CurrentPlayingClipIndex
        {
            get
            {
                return m_CurrentPlayingIndex;
            }

            set
            {
                if (value < 0 || value >= m_Clips.Count)
                    throw new System.IndexOutOfRangeException();

                m_CurrentPlayingIndex = value;
            }
        }

        public SequenceClip CurrentPlayingClip
        {
            get
            {
                return m_Clips[m_CurrentPlayingIndex];
            }
        }

        public string ElementName
        {
            get
            {
                return m_ElementName;
            }

            set
            {
                if (m_ElementName == value)
                    return;

                m_ElementName = value;

                if (m_Renderer != null && m_Renderer is UIDocumentRenderer)
                    (m_Renderer as UIDocumentRenderer).ReSearchElement(value);
            }
        }

        public bool IsPlaying
        {
            get
            {
                return m_IsPlaying;
            }
        }

        public bool IsPause
        {
            get
            {
                return m_IsPause;
            }
        }

        public float Duration
        {
            get
            {
                if (m_CurrentPlayingIndex >= 0 && m_CurrentPlayingIndex < m_Clips.Count)
                {
                    return CurrentPlayingClip.Length;
                }
                return 0;
            }
        }

        public float TimeStep
        {
            get
            {
                return m_Timer;
            }

            set
            {
                m_Timer = value;

                if (Application.isPlaying)
                    UpdateSprite();
            }
        }

        public float Progress
        {
            get
            {
                return TimeStep / Duration;
            }
        }

        public List<SequenceClip> Clips
        {
            get
            {
                return m_Clips;
            }
        }

        public SequenceRendererType SequenceRendererType
        {
            get
            {
                return m_SequenceRendererType;
            }

            set
            {
                if (value != m_SequenceRendererType)
                {
                    m_SequenceRendererType = value;
                    RendererDurty();
                }
            }
        }

        public SpriteRenderer SpriteRenderer
        {
            get
            {
                return m_SpriteRenderer;
            }

            set
            {
                if (m_SpriteRenderer != value)
                {
                    m_SpriteRenderer = value;
                    if (m_SequenceRendererType == SequenceRendererType.SpriteRenderer)
                        RendererDurty();
                }
            }
        }

        public MeshRenderer MeshRenderer
        {
            get
            {
                return m_MeshRenderer;
            }

            set
            {
                if (value != m_MeshRenderer)
                {
                    m_MeshRenderer = value;
                    if (m_SequenceRendererType == SequenceRendererType.MeshRenderer)
                        RendererDurty();
                }
            }
        }

        public SkinnedMeshRenderer SkinnedMeshRenderer
        {
            get
            {
                return m_SkinnedMeshRenderer;
            }

            set
            {
                if (value != m_SkinnedMeshRenderer)
                {
                    m_SkinnedMeshRenderer = value;
                    if (m_SequenceRendererType == SequenceRendererType.SkinnedMeshRenderer)
                        RendererDurty();
                }
            }
        }

        public UnityEngine.UI.Image Image
        {
            get
            {
                return m_Image;
            }
            set
            {
                if (value != m_Image)
                {
                    m_Image = value;
                    if (m_SequenceRendererType == SequenceRendererType.UGuiImage)
                        RendererDurty();
                }
            }
        }

        public UIDocument UiDocument
        {
            get
            {
                return m_UiDocument;
            }
            set
            {
                if (m_UiDocument != value)
                {
                    m_UiDocument = value;

                    if (m_SequenceRendererType == SequenceRendererType.UIElement)
                        RendererDurty();
                }
            }
        }

        public Material RenderingMaterial
        {
            get
            {
                return m_Material;
            }
            set
            {
                if (m_Material != value)
                {
                    m_Material = value;

                    if (m_SequenceRendererType == SequenceRendererType.Material)
                        RendererDurty();
                }
            }
        }

        private void Awake()
        {
            m_CurrentPlayingIndex = 0;
            RendererDurty();
        }

        private void OnEnable()
        {
            if (m_PlayOnEnable)
                Internal_Play();
        }

        private void OnDisable()
        {
            Internal_Stop();
        }

        private void Update()
        {
            if(m_RendererDurty)
                RecreateRenderer();

            if (m_IsPlaying && !m_IsPause)
            {
                UpdateDurty();
                UpdateTime();
            }

            if (m_UpdateDurty)
                UpdateSprite();
        }

        public void Play()
        {
            if (this.IsPause)
            {
                m_IsPause = false;
                return;
            }
            Internal_Play();
        }

        public void Play(int index)
        {
            if (m_Clips.Count <= index || index < 0)
                throw new System.IndexOutOfRangeException();

            m_CurrentPlayingIndex = Mathf.Clamp(index, 0, m_Clips.Count);
            Internal_Play();
        }

        public void Play(string clipName)
        {
            if (string.IsNullOrEmpty(clipName))
                throw new System.Exception("Clip name not find");

            m_CurrentPlayingIndex = m_Clips.FindIndex(c => c.name == clipName);
            Internal_Play();
        }

        public void Stop()
        {
            Internal_Stop(true);
            UpdateSprite();
        }

        public void Pause()
        {
            m_IsPause = true;
        }

        private void Internal_Play()
        {
            m_IsPause = false;
            m_IsPlaying = true;
            m_Timer = 0;
            m_Sign = 1;
            Internal_OnPlayStart();
        }

        private void Internal_Stop(bool resetTimer = true)
        {
            m_IsPause = false;
            m_IsPlaying = false;
            if (resetTimer)
                m_Timer = 0;
            m_Sign = 1;
            Internal_OnPlayStop();
        }

        private void Internal_OnPlayStart()
        {
            OnStartPlay.Invoke();
        }

        private void Internal_OnPlayStop()
        {
            OnEndPlay.Invoke();
        }

        private void UpdateTime(bool upDelta = true)
        {
            if (m_Timer < 0)
            {
                m_Timer = 0;
                return;
            }

            if (upDelta)
            {
                if (m_UpdateWithoutTimeScale)
                    m_Timer += Time.unscaledDeltaTime * m_Speed;
                else
                    m_Timer += Time.deltaTime * m_Speed;
            }

            if (m_Timer < 0 || m_Timer > CurrentPlayingClip.Length)
            {
                if (CurrentPlayingClip.wrap == WrapMode.PingPong)
                {
                    m_Sign *= -1;
                    m_Timer = -1;
                }
                else if (CurrentPlayingClip.wrap == WrapMode.Default || CurrentPlayingClip.wrap == WrapMode.Default)
                {
                    Internal_Stop(false);
                }
                else if (CurrentPlayingClip.wrap == WrapMode.Loop || CurrentPlayingClip.wrap == WrapMode.ClampForever)
                {
                    m_Sign = 1;
                    m_Timer = -1;
                }
            }
            m_Timer = Mathf.Clamp(m_Timer, 0, CurrentPlayingClip.Length);
        }

        private void UpdateSprite()
        {
            m_UpdateDurty = false;

            if (CurrentPlayingClip.FrameCount <= 0)
            {
                Debug.LogWarning("Current clip has no frame, can not update sprites!");
                return;
            }
            if (m_Renderer == null)
            {
                Debug.LogWarning("Animate element is not found!");
                return;
            }
            float t = m_Timer;
            if (m_Sign < 0)
            {
                t = Mathf.Clamp(CurrentPlayingClip.Length - m_Timer, 0, CurrentPlayingClip.Length);
            }
            Sprite frame = this.CurrentPlayingClip.GetFrameByTime(t);
            m_Renderer?.SetImage(frame);
        }

        private void Reset()
        {
            foreach (var component in gameObject.GetComponents<Component>())
            {
                if (component is UIDocument)
                {
                    m_SequenceRendererType = SequenceRendererType.UIElement;
                    m_UiDocument = component as UIDocument;
                    return;
                }
                else if (component is UnityEngine.UI.Image)
                {
                    m_SequenceRendererType = SequenceRendererType.UGuiImage;
                    m_Image = component as UnityEngine.UI.Image;
                    return;
                }
                else if (component is SpriteRenderer)
                {
                    m_SequenceRendererType = SequenceRendererType.SpriteRenderer;
                    m_SpriteRenderer = component as SpriteRenderer;
                    return;
                }
                else if (component is MeshRenderer)
                {
                    m_SequenceRendererType = SequenceRendererType.MeshRenderer;
                    m_MeshRenderer = component as MeshRenderer;
                    return;
                }
                else if (component is SkinnedMeshRenderer)
                {
                    m_SequenceRendererType = SequenceRendererType.SkinnedMeshRenderer;
                    m_SkinnedMeshRenderer = component as SkinnedMeshRenderer;
                    return;
                }
            }

            m_SequenceRendererType = default;
        }

        private void UpdateDurty()
        {
            m_UpdateDurty = true;
        }

        private void RendererDurty()
        {
            m_RendererDurty = true;
        }

        private void RecreateRenderer()
        {
            m_RendererDurty = false;
                
            if (m_Renderer != null)
            {
                m_Renderer.Dispose();
                m_Renderer = null;
            }

            if (m_UiDocument != null && m_SequenceRendererType == SequenceRendererType.UIElement)
            {
                var docr = new UIDocumentRenderer(m_UiDocument);
                m_Renderer = docr;
                docr.ReSearchElement(m_ElementName);
                UpdateDurty();
            }
            else if (m_Image != null && m_SequenceRendererType == SequenceRendererType.UGuiImage)
            {
                m_Renderer = new ImageRenderer(m_Image);
                UpdateDurty();
            }
            else if (m_SpriteRenderer != null && m_SequenceRendererType == SequenceRendererType.SpriteRenderer)
            {
                m_Renderer = new UnitySpriteRenderer(m_SpriteRenderer);
                UpdateDurty();
            }
            else if (m_MeshRenderer != null && m_SequenceRendererType == SequenceRendererType.MeshRenderer)
            {
                m_Renderer = new UnityMeshRenderer(m_MeshRenderer);
                UpdateDurty();
            }
            else if (m_SkinnedMeshRenderer != null && m_SequenceRendererType == SequenceRendererType.SkinnedMeshRenderer)
            {
                m_Renderer = new UnityMeshRenderer(m_SkinnedMeshRenderer);
                UpdateDurty();
            }
            else if (m_SequenceRendererType == SequenceRendererType.Material && m_Material != null)
            {
                m_Renderer = new MaterialRenderer(m_Material);
                UpdateDurty();
            }
        }
    }
}
